TypeScript 98.3%
CSS 0.9%
Shell 0.7%
1// Tableau de progression : maîtrise par concept/semaine, activité, série, statistiques.2import { NextResponse } from "next/server";3import { apiError, requireEnrollment } from "@/lib/api.ts";4import { requireUser } from "@/lib/auth/session.ts";5import { all, get } from "@/lib/db/index.ts";6import { masteryForCourse, LEVEL_LABELS } from "@/lib/learning/mastery.ts";7import { recommendations } from "@/lib/learning/recommend.ts";8import { normalizeCourse } from "@/lib/learning/helpers.ts";910export async function GET(_req: Request, ctx: { params: Promise<{ course: string }> }) {11 try {12 const user = await requireUser();13 const course = normalizeCourse((await ctx.params).course);14 requireEnrollment(user.id, course);1516 const mastery = masteryForCourse(user.id, course);17 const practiced = mastery.filter((m) => m.observations > 0);18 const globalScore = mastery.length19 ? mastery.reduce((s, m) => s + m.score * m.importance, 0) / mastery.reduce((s, m) => s + m.importance, 0)20 : 0;2122 // Par semaine23 const weeks = new Map<number, { score: number; n: number }>();24 for (const m of mastery) {25 const w = m.week ?? 0;26 const e = weeks.get(w) ?? { score: 0, n: 0 };27 e.score += m.score;28 e.n++;29 weeks.set(w, e);30 }3132 // Série de jours actifs33 const days = all<{ d: string }>(34 "SELECT DISTINCT date(created_at) as d FROM activity_log WHERE user_id = ? ORDER BY d DESC LIMIT 60",35 user.id36 ).map((r) => r.d);37 let streak = 0;38 const today = new Date();39 for (let i = 0; i < 60; i++) {40 const d = new Date(today);41 d.setDate(d.getDate() - i);42 const iso = d.toISOString().slice(0, 10);43 if (days.includes(iso)) streak++;44 else if (i > 0) break; // aujourd'hui pas encore actif ne casse pas la série d'hier45 }4647 const stats = {48 cardsReviewed: get<{ n: number }>(49 `SELECT COUNT(*) as n FROM review_log rl JOIN flashcards f ON f.id = rl.card_id WHERE rl.user_id = ? AND f.course_code = ?`,50 user.id, course51 )?.n ?? 0,52 quizAnswered: get<{ n: number }>(53 `SELECT COUNT(*) as n FROM quiz_answers qa JOIN quiz_sessions qs ON qs.id = qa.session_id WHERE qs.user_id = ? AND qs.course_code = ?`,54 user.id, course55 )?.n ?? 0,56 quizAccuracy: get<{ p: number }>(57 `SELECT ROUND(AVG(qa.correct)*100) as p FROM quiz_answers qa JOIN quiz_sessions qs ON qs.id = qa.session_id WHERE qs.user_id = ? AND qs.course_code = ?`,58 user.id, course59 )?.p ?? null,60 examAttempts: get<{ n: number }>(61 `SELECT COUNT(*) as n FROM exam_attempts ea JOIN mock_exams me ON me.id = ea.exam_id WHERE ea.user_id = ? AND me.course_code = ? AND ea.finished_at IS NOT NULL`,62 user.id, course63 )?.n ?? 0,64 studyMinutes: Math.round((get<{ s: number }>(65 "SELECT COALESCE(SUM(duration_s),0) as s FROM activity_log WHERE user_id = ? AND course_code = ?",66 user.id, course67 )?.s ?? 0) / 60),68 errorsToReview: get<{ n: number }>(69 "SELECT COUNT(*) as n FROM error_notebook WHERE user_id = ? AND course_code = ? AND status = 'a-revoir'",70 user.id, course71 )?.n ?? 0,72 cardsDue: get<{ n: number }>(73 `SELECT COUNT(*) as n FROM card_states cs JOIN flashcards f ON f.id = cs.card_id74 WHERE cs.user_id = ? AND f.course_code = ? AND cs.suspended = 0 AND cs.due_at <= datetime('now')`,75 user.id, course76 )?.n ?? 0,77 };7879 const recent = all(80 `SELECT kind, meta, created_at FROM activity_log WHERE user_id = ? AND course_code = ? ORDER BY id DESC LIMIT 10`,81 user.id, course82 );8384 return NextResponse.json({85 globalScore,86 levels: LEVEL_LABELS,87 concepts: mastery,88 practicedCount: practiced.length,89 weekScores: [...weeks.entries()].map(([week, v]) => ({ week, score: v.score / v.n })).sort((a, b) => a.week - b.week),90 streak,91 stats,92 recent,93 recommendations: recommendations(user.id, course),94 });95 } catch (e) {96 return apiError(e);97 }98}99